<?php
    namespace Think\Upload\Driver\Qiniu;

    class QiniuStorage {

        public $QINIU_RSF_HOST     =     'http://rsf.qbox.me';
        public $QINIU_RS_HOST     =     'http://rs.qbox.me';
        public $QINIU_UP_HOST     =     'http://up.qiniu.com';
        public $timeout         =     '';

        public function __construct($config){
            $this->sk         =     $config['secretKey'];
            $this->ak         =     $config['accessKey'];
            $this->domain     =     $config['domain'];
            $this->bucket     =     $config['bucket'];
            $this->timeout     =     isset($config['timeout'])? $config['timeout'] : 3600;
        }

        static function sign($sk, $ak, $data){
            $sign = hash_hmac('sha1', $data, $sk, true);
            return $ak . ':' . self::Qiniu_Encode($sign);
        }

        static function signWithData($sk, $ak, $data){
            $data = self::Qiniu_Encode($data);
            return self::sign($sk, $ak, $data) . ':' . $data;
        }

        public function accessToken($url, $body=''){
            $parsed_url =     parse_url($url);
            $path         =     $parsed_url['path'];
            $access     =     $path;
            if (isset($parsed_url['query'])) {
                $access .= "?" . $parsed_url['query'];
            }
            $access    .= "\n";

            if($body){
                $access .= $body;
            }
            return self::sign($this->sk, $this->ak, $access);
        }

        public function UploadToken($sk ,$ak ,$param){
            $param['deadline'] = $param['Expires'] == 0? 3600: $param['Expires'];
            $param['deadline'] += time();
            $data = array('scope'=> $this->bucket, 'deadline'=>$param['deadline']);
            if (!empty($param['CallbackUrl'])) {
                $data['callbackUrl'] = $param['CallbackUrl'];
            }
            if (!empty($param['CallbackBody'])) {
                $data['callbackBody'] = $param['CallbackBody'];
            }
            if (!empty($param['ReturnUrl'])) {
                $data['returnUrl'] = $param['ReturnUrl'];
            }
            if (!empty($param['ReturnBody'])) {
                $data['returnBody'] = $param['ReturnBody'];
            }
            if (!empty($param['AsyncOps'])) {
                $data['asyncOps'] = $param['AsyncOps'];
            }
            if (!empty($param['EndUser'])) {
                $data['endUser'] = $param['EndUser'];
            }
            $data = json_encode($data);
            return self::SignWithData($sk, $ak, $data);
        }

        public function upload($config, $file){
            $uploadToken = $this->UploadToken($this->sk, $this->ak, $config);

            $url     =     "{$this->QINIU_UP_HOST}";
            $mimeBoundary = md5(microtime());
            $header =     array('Content-Type'=>'multipart/form-data;boundary='.$mimeBoundary);
            $data     =     array();

            $fields = array(
                'token'    =>    $uploadToken,
                'key'    =>    $config['saveName']? : $file['fileName'],
            );

            if(is_array($config['custom_fields']) && $config['custom_fields'] !== array()){
                $fields = array_merge($fields, $config['custom_fields']);
            }

            foreach ($fields as $name => $val) {
                array_push($data, '--' . $mimeBoundary);
                array_push($data, "Content-Disposition: form-data; name=\"$name\"");
                array_push($data, '');
                array_push($data, $val);
            }

            //文件
            array_push($data, '--' . $mimeBoundary);
            $name         =     $file['name'];
            $fileName     =     $file['fileName'];
            $fileBody     =     $file['fileBody'];
            $fileName     =     self::Qiniu_escapeQuotes($fileName);
            array_push($data, "Content-Disposition: form-data; name=\"$name\"; filename=\"$fileName\"");
            array_push($data, 'Content-Type: application/octet-stream');
            array_push($data, '');
            array_push($data, $fileBody);

            array_push($data, '--' . $mimeBoundary . '--');
            array_push($data, '');

            $body         =     implode("\r\n", $data);
            $response     =     $this->request($url, 'POST', $header, $body);
            return $response;
        }

        public function dealWithType($key, $type){
            $param         =     $this->buildUrlParam();
            $url         =     '';

            switch($type){
                case 'img':
                    $url = $this->downLink($key);
                    if($param['imageInfo']){
                        $url .= '?imageInfo';
                    }else if($param['exif']){
                        $url .= '?exif';
                    }else if($param['imageView']){
                        $url .= '?imageView/'.$param['mode'];
                        if($param['w'])
                            $url .= "/w/{$param['w']}";
                        if($param['h'])
                            $url .= "/h/{$param['h']}";
                        if($param['q'])
                            $url .= "/q/{$param['q']}";
                        if($param['format'])
                            $url .= "/format/{$param['format']}";
                    }
                    break;
                case 'video': //TODO 视频处理
                case 'doc':
                    $url = $this->downLink($key);
                    $url .= '?md2html';
                    if(isset($param['mode']))
                        $url .= '/'.(int)$param['mode'];
                    if($param['cssurl'])
                        $url .= '/'. self::Qiniu_Encode($param['cssurl']);
                    break;

            }
            return $url;
        }

        public function buildUrlParam(){
            return $_REQUEST;
        }

        //获取某个路径下的文件列表
        public function getList($query = array(), $path = ''){
            $query             =     array_merge(array('bucket'=>$this->bucket), $query);
            $url             =     "{$this->QINIU_RSF_HOST}/list?".http_build_query($query);
            $accessToken     =     $this->accessToken($url);
            $response         =     $this->request($url, 'POST', array('Authorization'=>"QBox $accessToken"));
            return $response;
        }

        //获取某个文件的信息
        public function info($key){
            $key             =     trim($key);
            $url             =     "{$this->QINIU_RS_HOST}/stat/" . self::Qiniu_Encode("{$this->bucket}:{$key}");
            $accessToken     =     $this->accessToken($url);
            $response         =     $this->request($url, 'POST', array(
                'Authorization'     =>    "QBox $accessToken",
            ));
            return $response;
        }

        //获取文件下载资源链接
        public function downLink($key){
            $key = urlencode($key);
            $key = self::Qiniu_escapeQuotes($key);
            $url = "http://{$this->domain}/{$key}";
            return $url;
        }

        //重命名单个文件
        public function rename($file, $new_file){
            $key = trim($file);
            $url = "{$this->QINIU_RS_HOST}/move/" . self::Qiniu_Encode("{$this->bucket}:{$key}") .'/'. self::Qiniu_Encode("{$this->bucket}:{$new_file}");
            trace($url);
            $accessToken = $this->accessToken($url);
            $response = $this->request($url, 'POST', array('Authorization'=>"QBox $accessToken"));
            return $response;
        }

        //删除单个文件
        public function del($file){
            $key = trim($file);
            $url = "{$this->QINIU_RS_HOST}/delete/" . self::Qiniu_Encode("{$this->bucket}:{$key}");
            $accessToken = $this->accessToken($url);
            $response = $this->request($url, 'POST', array('Authorization'=>"QBox $accessToken"));
            return $response;
        }

        //批量删除文件
        public function delBatch($files){
            $url = $this->QINIU_RS_HOST . '/batch';
            $ops = array();
            foreach ($files as $file) {
                $ops[] = "/delete/". self::Qiniu_Encode("{$this->bucket}:{$file}");
            }
            $params = 'op=' . implode('&op=', $ops);
            $url .= '?'.$params;
            trace($url);
            $accessToken = $this->accessToken($url);
            $response = $this->request($url, 'POST', array('Authorization'=>"QBox $accessToken"));
            return $response;
        }

        static function Qiniu_Encode($str) {// URLSafeBase64Encode
            $find = array('+', '/');
            $replace = array('-', '_');
            return str_replace($find, $replace, base64_encode($str));
        }

        static function Qiniu_escapeQuotes($str){
            $find = array("\\", "\"");
            $replace = array("\\\\", "\\\"");
            return str_replace($find, $replace, $str);
        }

        /**
         * 请求云服务器
         * @param  string   $path    请求的PATH
         * @param  string   $method  请求方法
         * @param  array    $headers 请求header
         * @param  resource $body    上传文件资源
         * @return boolean
         */
        private function request($path, $method, $headers = null, $body = null){
            $ch  = curl_init($path);

            $_headers = array('Expect:');
            if (!is_null($headers) && is_array($headers)){
                foreach($headers as $k => $v) {
                    array_push($_headers, "{$k}: {$v}");
                }
            }

            $length = 0;
            $date   = gmdate('D, d M Y H:i:s \G\M\T');

            if (!is_null($body)) {
                if(is_resource($body)){
                    fseek($body, 0, SEEK_END);
                    $length = ftell($body);
                    fseek($body, 0);

                    array_push($_headers, "Content-Length: {$length}");
                    curl_setopt($ch, CURLOPT_INFILE, $body);
                    curl_setopt($ch, CURLOPT_INFILESIZE, $length);
                } else {
                    $length = @strlen($body);
                    array_push($_headers, "Content-Length: {$length}");
                    curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
                }
            } else {
                array_push($_headers, "Content-Length: {$length}");
            }

            // array_push($_headers, 'Authorization: ' . $this->sign($method, $uri, $date, $length));
            array_push($_headers, "Date: {$date}");

            curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers);
            curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
            curl_setopt($ch, CURLOPT_HEADER, 1);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);

            if ($method == 'PUT' || $method == 'POST') {
                curl_setopt($ch, CURLOPT_POST, 1);
            } else {
                curl_setopt($ch, CURLOPT_POST, 0);
            }

            if ($method == 'HEAD') {
                curl_setopt($ch, CURLOPT_NOBODY, true);
            }

            $response = curl_exec($ch);
            $status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
            list($header, $body) = explode("\r\n\r\n", $response, 2);
            if ($status == 200) {
                if ($method == 'GET') {
                    return $body;
                } else {
                    return $this->response($response);
                }
            } else {
                $this->error($header , $body);
                return false;
            }
        }

        /**
         * 获取响应数据
         * @param  string $text 响应头字符串
         * @return array        响应数据列表
         */
        private function response($text){
            $headers = explode(PHP_EOL, $text);
            $items = array();
            foreach($headers as $header) {
                $header = trim($header);
                if(strpos($header, '{') !== False){
                    $items = json_decode($header, 1);
                    break;
                }
            }
            return $items;
        }

        /**
         * 获取请求错误信息
         * @param  string $header 请求返回头信息
         */
        private function error($header, $body) {
            list($status, $stash) = explode("\r\n", $header, 2);
            list($v, $code, $message) = explode(" ", $status, 3);
            $message = is_null($message) ? 'File Not Found' : "[{$status}]:{$message}]";
            $this->error = $message;
            $this->errorStr = json_decode($body ,1);
            $this->errorStr = $this->errorStr['error'];
        }
    }
